The thread safety of lazy variables in Swift

--

Foreword

This article was written based on my personal perspective. Occasionally I found the usage of lazy variables from pull requests, and most of the times the thread safety issue of them was not properly handled. I hope this short article could give you a general idea of the problem and hopefully help you to make your own decision of using or not-using lazy variables in your own context.

Lazy variables

Lazy variables, in contrast to the regular ones, are not initialized and loaded into the memory when it’s declared and defined. Instead, they are initialized at the first access of it. Suppose there is a class:

A sample class to illustrate lazy variables

The property aLazyInstance is not loaded into the memory when an instance MyClass is created. However, if somewhere access the property of the MyClass instance created, the property is instantly loaded into the memory at the very same time.

Using wisely of lazy variables may reduce the memory footprint of your project. Likewise, you may leverage lazy loading to avoid loading lots of data into the memory for some reasons. However, unlike using regular variables, it’s better to think thoroughly the pros and cons before using lazy variables.

What may go wrong in case of multithreading

Let’s take a look at the following code. Support there is an instance of SharedInstanceClass shared across threads, and the property testVar is accessible (for simplicity, we use package access modifier)

A simple piece of code to demonstrate race condition across threads

By observing the output, you may notice that the value of testVar may be changed. In most of the case, that is not what a developer expects. Here is a sample output when a race condition happens:

3 580 51 32 184 185 186 188 187 189 18

As you see, the thread with id = 3 set testVar to 58, then the thread with id = 0 set testVar to 5, and then thread with id = 1 set testVar to 3. And then, the value of testVar remained 18.

To lazy or not: My rule of thumb

Here is my own rule of thumb if I have to decide to make a variable lazy or not. If you have your own rules to do that, that’s great! If you don’t, then I hope mine could give you some hints so you can later get your own insight on the problem.

Question 1: Is it really going to reduce the memory footprint?

Declaring a variable as lazy doe not necessarily imply a reduction of memory footprint. If accessing the property is soon and inevitable, then lazy loading just won’t do any good.

Question 2: Is the lazy variable going to be accessed across distinct threads?

Think about the scenes where the lazy variable is accessed. If it works with concurrency by its nature, then you have to protect the lazy var, with some sort of synchronization facilities, to avoid unexpected behaviors or even corrupted values. Or, if the resources required to protect such lazy variable are much more than the variable (e.g. using a dispatch queue to protect a boolean variable), perhaps not making the variable lazy is better.

If it is not designed to work concurrency, then be defensive on designing interfaces that reduce the chance of misusing such lazy variable. Or even better, when there is no one needs to access the lazy property outside the object, make it private to avoid side-effects outside the object.

Conclusion

Although lazy loading may help developers to reduce memory footprint, to avoid loading too many data into the memory, or even to work around some sort of initialization or setup problems (such as setup views when you need to access self when the instance of self is not initialized). Nevertheless, applying lazy loading is not always toll-free: sometimes you will have to go extra miles to avoid side-effects. Lastly, don’t be afraid of lazy variables. it’s still a wonderful thing that can help you for the purpose of its existence. Cheers!

--

--